﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using JetBrains.Annotations;

namespace DynamicProxyCore
{
	public static class ReflectionExtention
	{
		static readonly Dictionary<ComposedKey, Func<object, object>> _getCache = new Dictionary<ComposedKey, Func<object, object>>();
		static readonly Dictionary<ComposedKey, Action<object, object>> _setCache = new Dictionary<ComposedKey, Action<object, object>>();
		static readonly Dictionary<Type, PropertyInfo[]> _propertyCache = new Dictionary<Type, PropertyInfo[]>();
		static readonly Dictionary<string, Type> _typeCache = new Dictionary<string, Type>();

		public static Type GetTypeByName(this string name)
		{
			return _typeCache.TryGet(name, () => Type.GetType(name));
		}

		public static IEnumerable<PropertyInfo> GetPropertiesFast(this Type type)
		{
			return GetPropertiesFast(type, p => true);
		}

		public static IEnumerable<PropertyInfo> GetPropertiesFast(this Type type, Func<PropertyInfo,bool> filter)
		{
			return _propertyCache.TryGet(type,()=> type.GetProperties().Where(filter).ToArray());
        }

        [DebuggerStepThrough]
		public static object GetProperty(this object obj, string name)
		{
			var key = new ComposedKey(obj.GetType(), name);
			return _getCache.TryGet(key, () =>
			{
				var lambdaObj = Expression.Parameter(typeof(object), "obj");
				var castObjExpression = Expression.Convert(lambdaObj, obj.GetType());

				var expression = Expression.Lambda<Func<object, object>>(Expression.Convert(Expression.Property(castObjExpression, name), typeof(object)), lambdaObj);
				return expression.Compile();
			})(obj);
		}

        [DebuggerStepThrough]
		public static void SetProperty(this object obj, [NotNull] string name, object value)
		{
			obj.SetProperty(name,obj.GetType().GetProperty(name).PropertyType,value);
		}

        [DebuggerStepThrough]
		public static void SetProperty(this object obj, [NotNull] string name, [NotNull] Type valueType, object value)
		{
			var key = new ComposedKey(obj.GetType(), name, valueType);
			_setCache.TryGet(key, () =>
			{
				var lambdaObj = Expression.Parameter(typeof(object), "obj");
				var lambdaValue = Expression.Parameter(typeof(object), "value");
				var castObjExpression = Expression.Convert(lambdaObj, obj.GetType());
				var castValueExpression = Expression.Convert(lambdaValue, valueType);

				var expression = Expression.Lambda<Action<object, object>>(Expression.Assign(Expression.Property(castObjExpression, name), castValueExpression), lambdaObj, lambdaValue);
				return expression.Compile();
			})(obj, value);

		}
	}
}
